//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace Microsoft.Research.ScientificWorkflow.WorkflowApplicationCommonSL
{
    /// <summary>
    /// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
    /// It also implements the <see cref="IActiveAware"/> interface, which is
    /// useful when registering this command in a <see cref="CompositeCommand"/>
    /// that monitors command's activity.
    /// </summary>
    /// <typeparam name="T">Parameter type.</typeparam>
    public partial class DelegateCommand<T> : ICommand
    {
        private readonly Action<T> executeMethod = null;
        private readonly Func<T, bool> canExecuteMethod = null;

        /// <summary>
        /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
        /// </summary>
        /// <param name="executeMethod">Delegate to execute when Execute is called on the command.  This can be null to just hook up a CanExecute delegate.</param>
        /// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
        public DelegateCommand(Action<T> executeMethod)
            : this(executeMethod, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
        /// </summary>
        /// <param name="executeMethod">Delegate to execute when Execute is called on the command.  This can be null to just hook up a CanExecute delegate.</param>
        /// <param name="canExecuteMethod">Delegate to execute when CanExecute is called on the command.  This can be null.</param>
        /// <exception cref="ArgumentNullException">When both <paramref name="executeMethod"/> and <paramref name="canExecuteMethod"/> ar <see langword="null" />.</exception>
        public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
        {
            if (executeMethod == null && canExecuteMethod == null)
                throw new ArgumentNullException("executeMethod", "Both the executeMethod and the canExecuteMethod delegates cannot be null");

            this.executeMethod = executeMethod;
            this.canExecuteMethod = canExecuteMethod;
        }

        ///<summary>
        ///Defines the method that determines whether the command can execute in its current state.
        ///</summary>
        ///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
        ///<returns>
        ///<see langword="true" /> if this command can be executed; otherwise, <see langword="false" />.
        ///</returns>
        public bool CanExecute(T parameter)
        {
            if (canExecuteMethod == null) return true;
            return canExecuteMethod(parameter);
        }

        ///<summary>
        ///Defines the method to be called when the command is invoked.
        ///</summary>
        ///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
        public void Execute(T parameter)
        {
            if (executeMethod == null) return;
            executeMethod(parameter);
        }

        ///<summary>
        ///Defines the method that determines whether the command can execute in its current state.
        ///</summary>
        ///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        ///<returns>
        ///true if this command can be executed; otherwise, false.
        ///</returns>
        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute((T)parameter);
        }

        ///<summary>
        ///Occurs when changes occur that affect whether or not the command should execute.
        ///</summary>
        public event EventHandler CanExecuteChanged;

        ///<summary>
        ///Defines the method to be called when the command is invoked.
        ///</summary>
        ///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        void ICommand.Execute(object parameter)
        {
            Execute((T)parameter);
        }

        /// <summary>
        /// Raises <see cref="CanExecuteChanged"/> on the UI thread so every command invoker
        /// can requery to check if the command can execute.
        /// <remarks>Note that this will trigger the execution of <see cref="CanExecute"/> once for each invoker.</remarks>
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged();
        }

        /// <summary>
        /// Raises <see cref="CanExecuteChanged"/> on the UI thread so every command invoker can requery to check if the command can execute.
        /// </summary>
        protected virtual void OnCanExecuteChanged()
        {
            System.Windows.Threading.Dispatcher dispatcher = null;
            if (Deployment.Current != null)
            {
                dispatcher = Deployment.Current.Dispatcher;
            }

            EventHandler canExecuteChangedHandler = CanExecuteChanged;
            if (canExecuteChangedHandler != null)
            {
                if (dispatcher != null && !dispatcher.CheckAccess())
                {
                    dispatcher.BeginInvoke((Action)OnCanExecuteChanged);
                }
                else
                {
                    canExecuteChangedHandler(this, EventArgs.Empty);
                }
            }
        }
    }

    public static class Commands
    {
        private static readonly DependencyProperty CommandButtonBehaviorProperty =
            DependencyProperty.RegisterAttached("CommandButtonBehavior", typeof(CommandButtonBehavior),
                                                typeof(Commands), null);

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Commands),
            new PropertyMetadata(CommandPropertyChanged));

        public static ICommand GetCommand(DependencyObject buttonBase)
        {
            return (ICommand)buttonBase.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject buttonBase, ICommand value)
        {
            buttonBase.SetValue(CommandProperty, value);
        }


        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(Commands),
            null);

        public static object GetCommandParameter(DependencyObject buttonBase)
        {
            return buttonBase.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(DependencyObject buttonBase, object value)
        {
            buttonBase.SetValue(CommandParameterProperty, value);
        }

        private static void CommandPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ButtonBase element = o as ButtonBase;
            if (element != null)
            {
                if (e.OldValue != null)
                {
                    UnhookCommand(element, (ICommand)e.OldValue);
                }
                if (e.NewValue != null)
                {
                    HookCommand(element, (ICommand)e.NewValue);
                }
            }
        }

        private static void HookCommand(ButtonBase element, ICommand command)
        {
            CommandButtonBehavior behavior = new CommandButtonBehavior(element, command);
            behavior.Attach();
            element.SetValue(CommandButtonBehaviorProperty, behavior);
        }

        private static void UnhookCommand(ButtonBase element, ICommand command)
        {
            CommandButtonBehavior behavior = (CommandButtonBehavior)element.GetValue(CommandButtonBehaviorProperty);
            behavior.Detach();
            element.ClearValue(CommandButtonBehaviorProperty);
        }



        private class CommandButtonBehavior
        {
            private readonly WeakReference elementReference;
            private readonly ICommand command;

            public CommandButtonBehavior(ButtonBase element, ICommand command)
            {
                this.elementReference = new WeakReference(element);
                this.command = command;
            }

            public void Attach()
            {
                ButtonBase element = GetElement();
                if (element != null)
                {
                    element.Click += element_Clicked;
                    command.CanExecuteChanged += command_CanExecuteChanged;
                    SetIsEnabled(element);
                }
            }

            public void Detach()
            {
                command.CanExecuteChanged -= command_CanExecuteChanged;
                ButtonBase element = GetElement();
                if (element != null)
                {
                    element.Click -= element_Clicked;
                }
            }

            void command_CanExecuteChanged(object sender, EventArgs e)
            {
                ButtonBase element = GetElement();
                if (element != null)
                {
                    SetIsEnabled(element);
                }
                else
                {
                    Detach();
                }
            }

            private void SetIsEnabled(ButtonBase element)
            {
                element.IsEnabled = command.CanExecute(element.GetValue(Commands.CommandParameterProperty));
            }

            private static void element_Clicked(object sender, EventArgs e)
            {
                DependencyObject element = (DependencyObject)sender;
                ICommand command = (ICommand)element.GetValue(CommandProperty);
                object commandParameter = element.GetValue(CommandParameterProperty);
                command.Execute(commandParameter);
            }

            private ButtonBase GetElement()
            {
                return elementReference.Target as ButtonBase;
            }
        }
    }
}
